/*
 * PL/Python 主要入口点
 *
 * src/pl/plpython/plpy_main.c
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "plpy_elog.h"
#include "plpy_exec.h"
#include "plpy_main.h"
#include "plpy_plpymodule.h"
#include "plpy_procedure.h"
#include "plpy_subxactobject.h"
#include "plpython.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"

/*
 * 导出的函数
 */

extern void _PG_init(void);

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(plpython3_validator);
PG_FUNCTION_INFO_V1(plpython3_call_handler);
PG_FUNCTION_INFO_V1(plpython3_inline_handler);


static bool fc_PLy_procedure_is_trigger(Form_pg_proc fc_procStruct);
static void fc_plpython_error_callback(void *fc_arg);
static void fc_plpython_inline_error_callback(void *fc_arg);
static void fc_PLy_init_interp(void);

static PLyExecutionContext *fc_PLy_push_execution_context(bool fc_atomic_context);
static void fc_PLy_pop_execution_context(void);

/* Python 库冲突检测的静态状态 */
static int *plpython_version_bitmask_ptr = NULL;
static int	plpython_version_bitmask = 0;

/* 初始化全局变量 */
PyObject   *PLy_interp_globals = NULL;

/* 这不需要是全局的；使用 PLy_current_execution_context() */
static PLyExecutionContext *PLy_execution_contexts = NULL;


void _PG_init(void)
{
	int		  **fc_bitmask_ptr;

	/*
	 * 设置一个共享位掩码变量，告诉我们哪个 Python 版本
	 * 被加载到该进程的地址空间中。如果有多个版本，出于避免崩溃的考虑，
	 * 我们不能调用 libpython。但推迟实际的失败，以便像 pg_restore 
	 * 这样的操作可以加载多个 plpython 库，只要它们不尝试做
	 * 过多的语言相关操作。
	 *
	 * 虽然我们现在只支持 Python 3，但有人可能会创建一个
	 * 额外的版本，为 Python 2 恢复支持。应检测到与
	 * 这种扩展的冲突。
	 */
	fc_bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask");
	if (!(*fc_bitmask_ptr))		/* 我是第一个吗？ */
		*fc_bitmask_ptr = &plpython_version_bitmask;
	/* 保留指向约定的共享变量的指针... */
	plpython_version_bitmask_ptr = *fc_bitmask_ptr;
	/* ...并宣布我的存在 */
	*plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION);

	/*
	 * 即使在存在冲突的 plpython 的情况下，这也应该是安全的，
	 * 并且在可能抛出冲突错误之前执行此操作是必要的，
	 * 否则错误信息将不会被本地化。
	 */
	pg_bindtextdomain(TEXTDOMAIN);
}

/*
 * 在检查与其他 Python 版本的冲突后，执行一次性设置 PL/Python。
 */
static void fc_PLy_initialize(void)
{
	static bool fc_inited = false;

	/*
	 * 在主动对 libpython 执行任何操作之前检查是否有多个 Python 库。
	 * 每次进入 PL/Python 时都必须重复此步骤，以防自上次查看以来加载了
	 * 冲突的库。
	 *
	 * 将此错误的严重性从 FATAL 降为 ERROR 是很有吸引力的，
	 * 但会有边缘情况，因此最好还是保守一些。
	 */
	if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION))
		ereport(FATAL,
				(errmsg("multiple Python libraries are present in session"),
				 errdetail("Only one Python major version can be used in one session.")));

	/* 其余部分每个会话只应执行一次 */
	if (fc_inited)
		return;

	PyImport_AppendInittab("plpy", PyInit_plpy);
	Py_Initialize();
	PyImport_ImportModule("plpy");
	fc_PLy_init_interp();
	PLy_init_plpy();
	if (PyErr_Occurred())
		PLy_elog(FATAL, "untrapped error in initialization");

	init_procedure_caches();

	explicit_subtransactions = NIL;

	PLy_execution_contexts = NULL;

	fc_inited = true;
}

/*
 * 这应该仅从 PLy_initialize 调用一次。初始化 Python
 * 解释器和全局数据。
 */
static void fc_PLy_init_interp(void)
{
	static PyObject *fc_PLy_interp_safe_globals = NULL;
	PyObject   *fc_mainmod;

	fc_mainmod = PyImport_AddModule("__main__");
	if (fc_mainmod == NULL || PyErr_Occurred())
		PLy_elog(ERROR, "could not import \"__main__\" module");
	Py_INCREF(fc_mainmod);
	PLy_interp_globals = PyModule_GetDict(fc_mainmod);
	fc_PLy_interp_safe_globals = PyDict_New();
	if (fc_PLy_interp_safe_globals == NULL)
		PLy_elog(ERROR, NULL);
	PyDict_SetItemString(PLy_interp_globals, "GD", fc_PLy_interp_safe_globals);
	Py_DECREF(fc_mainmod);
	if (PLy_interp_globals == NULL || PyErr_Occurred())
		PLy_elog(ERROR, "could not initialize globals");
}

Datum plpython3_validator(PG_FUNCTION_ARGS)
{
	Oid			fc_funcoid = PG_GETARG_OID(0);
	HeapTuple	fc_tuple;
	Form_pg_proc fc_procStruct;
	bool		fc_is_trigger;

	if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, fc_funcoid))
		PG_RETURN_VOID();

	if (!check_function_bodies)
		PG_RETURN_VOID();

	/* 仅在确保需要做某事后执行此操作 */
	fc_PLy_initialize();

	/* 获取新函数的pg_proc条目 */
	fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcoid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for function %u", fc_funcoid);
	fc_procStruct = (Form_pg_proc) GETSTRUCT(fc_tuple);

	fc_is_trigger = fc_PLy_procedure_is_trigger(fc_procStruct);

	ReleaseSysCache(fc_tuple);

	/* 我们无法对任何特定表验证触发器... */
	PLy_procedure_get(fc_funcoid, InvalidOid, fc_is_trigger);

	PG_RETURN_VOID();
}

Datum plpython3_call_handler(PG_FUNCTION_ARGS)
{
	bool		fc_nonatomic;
	Datum		fc_retval;
	PLyExecutionContext *fc_exec_ctx;
	ErrorContextCallback fc_plerrcontext;

	fc_PLy_initialize();

	fc_nonatomic = fcinfo->context &&
		IsA(fcinfo->context, CallContext) &&
		!castNode(CallContext, fcinfo->context)->atomic;

	/* 注意：SPI_finish() 发生在 plpy_exec.c 中，这种设计值得怀疑 */
	if (SPI_connect_ext(fc_nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed");

	/*
	 * 将执行上下文压入堆栈。重要的是，必须在此之后将其弹出，
	 * 因此要避免在这里和 PG_TRY 之间放置任何可能抛出错误的内容。
	 */
	fc_exec_ctx = fc_PLy_push_execution_context(!fc_nonatomic);

	PG_TRY();
	{
		Oid			fc_funcoid = fcinfo->flinfo->fn_oid;
		PLyProcedure *fc_proc;

		/*
		 * 为 ereport() 设置错误追踪支持。注意，PG_TRY
		 * 结构在退出时会为我们再次弹出这个，因此我们不需要
		 * 明确执行此操作，也不必担心回调在我们
		 * 销毁 exec_ctx 后被调用。
		 */
		fc_plerrcontext.callback = fc_plpython_error_callback;
		fc_plerrcontext.arg = fc_exec_ctx;
		fc_plerrcontext.previous = error_context_stack;
		error_context_stack = &fc_plerrcontext;

		if (CALLED_AS_TRIGGER(fcinfo))
		{
			Relation	fc_tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
			HeapTuple	fc_trv;

			fc_proc = PLy_procedure_get(fc_funcoid, RelationGetRelid(fc_tgrel), true);
			fc_exec_ctx->curr_proc = fc_proc;
			fc_trv = PLy_exec_trigger(fcinfo, fc_proc);
			fc_retval = PointerGetDatum(fc_trv);
		}
		else
		{
			fc_proc = PLy_procedure_get(fc_funcoid, InvalidOid, false);
			fc_exec_ctx->curr_proc = fc_proc;
			fc_retval = PLy_exec_function(fcinfo, fc_proc);
		}
	}
	PG_CATCH();
	{
		fc_PLy_pop_execution_context();
		PyErr_Clear();
		PG_RE_THROW();
	}
	PG_END_TRY();

	/* 销毁执行上下文 */
	fc_PLy_pop_execution_context();

	return fc_retval;
}

Datum plpython3_inline_handler(PG_FUNCTION_ARGS)
{
	LOCAL_FCINFO(fc_fake_fcinfo, 0);
	InlineCodeBlock *fc_codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
	FmgrInfo	fc_flinfo;
	PLyProcedure fc_proc;
	PLyExecutionContext *fc_exec_ctx;
	ErrorContextCallback fc_plerrcontext;

	fc_PLy_initialize();

	/* 注意：SPI_finish() 发生在 plpy_exec.c 中，这种设计值得怀疑 */
	if (SPI_connect_ext(fc_codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed");

	MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
	MemSet(&fc_flinfo, 0, sizeof(fc_flinfo));
	fc_fake_fcinfo->flinfo = &fc_flinfo;
	fc_flinfo.fn_oid = InvalidOid;
	fc_flinfo.fn_mcxt = CurrentMemoryContext;

	MemSet(&fc_proc, 0, sizeof(PLyProcedure));
	fc_proc.mcxt = AllocSetContextCreate(TopMemoryContext,
									  "__plpython_inline_block",
									  ALLOCSET_DEFAULT_SIZES);
	fc_proc.pyname = MemoryContextStrdup(fc_proc.mcxt, "__plpython_inline_block");
	fc_proc.langid = fc_codeblock->langOid;

	/*
	 * 这目前足以让 PLy_exec_function 工作，但
	 * 将来我们可能需要诚实地使用 PLy_output_setup_func。
	 */
	fc_proc.result.typoid = VOIDOID;

	/*
	 * 将执行上下文压入堆栈。重要的是，必须在此之后将其弹出，
	 * 因此要避免在这里和 PG_TRY 之间放置任何可能抛出错误的内容。
	 */
	fc_exec_ctx = fc_PLy_push_execution_context(fc_codeblock->atomic);

	PG_TRY();
	{
		/*
		 * 为 ereport() 设置错误回溯支持。
		 * plpython_inline_error_callback 当前不需要 exec_ctx，但
		 * 为了与 plpython_call_handler 一致，我们采用相同的方式。
		 */
		fc_plerrcontext.callback = fc_plpython_inline_error_callback;
		fc_plerrcontext.arg = fc_exec_ctx;
		fc_plerrcontext.previous = error_context_stack;
		error_context_stack = &fc_plerrcontext;

		PLy_procedure_compile(&fc_proc, fc_codeblock->source_text);
		fc_exec_ctx->curr_proc = &fc_proc;
		PLy_exec_function(fc_fake_fcinfo, &fc_proc);
	}
	PG_CATCH();
	{
		fc_PLy_pop_execution_context();
		PLy_procedure_delete(&fc_proc);
		PyErr_Clear();
		PG_RE_THROW();
	}
	PG_END_TRY();

	/* 销毁执行上下文 */
	fc_PLy_pop_execution_context();

	/* 现在清理我们创建的临时过程 */
	PLy_procedure_delete(&fc_proc);

	PG_RETURN_VOID();
}

static bool fc_PLy_procedure_is_trigger(Form_pg_proc fc_procStruct)
{
	return (fc_procStruct->prorettype == TRIGGEROID);
}

static void fc_plpython_error_callback(void *fc_arg)
{
	PLyExecutionContext *fc_exec_ctx = (PLyExecutionContext *) fc_arg;

	if (fc_exec_ctx->curr_proc)
	{
		if (fc_exec_ctx->curr_proc->is_procedure)
			errcontext("PL/Python procedure \"%s\"",
					   PLy_procedure_name(fc_exec_ctx->curr_proc));
		else
			errcontext("PL/Python function \"%s\"",
					   PLy_procedure_name(fc_exec_ctx->curr_proc));
	}
}

static void fc_plpython_inline_error_callback(void *fc_arg)
{
	errcontext("PL/Python anonymous code block");
}

PLyExecutionContext *
PLy_current_execution_context(void)
{
	if (PLy_execution_contexts == NULL)
		elog(ERROR, "no Python function is currently executing");

	return PLy_execution_contexts;
}

MemoryContext PLy_get_scratch_context(PLyExecutionContext *fc_context)
{
	/*
	 * 在某个 plpython 过程中特定情境下可能根本不需要临时上下文，
	 * 因此在第一次请求时分配它。
	 */
	if (fc_context->scratch_ctx == NULL)
		fc_context->scratch_ctx =
			AllocSetContextCreate(TopTransactionContext,
								  "PL/Python scratch context",
								  ALLOCSET_DEFAULT_SIZES);
	return fc_context->scratch_ctx;
}

static PLyExecutionContext *
fc_PLy_push_execution_context(bool fc_atomic_context)
{
	PLyExecutionContext *fc_context;

	/* 选择一个与 SPI 使用的内存上下文类似的上下文。 */
	fc_context = (PLyExecutionContext *)
		MemoryContextAlloc(fc_atomic_context ? TopTransactionContext : PortalContext,
						   sizeof(PLyExecutionContext));
	fc_context->curr_proc = NULL;
	fc_context->scratch_ctx = NULL;
	fc_context->next = PLy_execution_contexts;
	PLy_execution_contexts = fc_context;
	return fc_context;
}

static void fc_PLy_pop_execution_context(void)
{
	PLyExecutionContext *fc_context = PLy_execution_contexts;

	if (fc_context == NULL)
		elog(ERROR, "no Python function is currently executing");

	PLy_execution_contexts = fc_context->next;

	if (fc_context->scratch_ctx)
		MemoryContextDelete(fc_context->scratch_ctx);
	pfree(fc_context);
}
